package com.baosight.risk.base.redis;


import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import redis.clients.jedis.Jedis;

/**
 * redis+lua分布式锁
 * lock 锁
 * unLock 释放锁
 *
 * @author 黎秋荣
 */
@Service
public class RedisLock {


    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    private final String intoScript = "local key = KEYS[1];"
            + "local timeout = KEYS[2];"
            + "local res = redis.call('get', key);"
            + "if res == '0' then "
            + "return -1;"
            + "else "
            + "local setres = redis.call('set', key, 0);"
            + "local exp_res = redis.call('pexpire', key, timeout);"
            + "if exp_res == 1 then "
            + "return 0;"
            + "end;"
            + "end;"
            + "return -1;";

    private final String outScript = "local key = KEYS[1];"
            + "local setres = redis.call('set', key, 1);"
            + "if setres['ok'] == 'OK' then "
            + "return 0;"
            + "end;"
            + "return -1;";

    private final String time = "10000";

    private final long zero = 0L;

    private final long minusOne = -1L;

    private final long minusTwo = -2L;

    private final String nil = "nil";

    /**
     * 锁
     *
     * @param key
     * @param timeout 失效时间,默认10秒
     * @return -1 key已存在  0 key不存在  -2 执行命令失败
     */
    public long lock(final String key, Long timeout) {
        if (StringUtils.isEmpty(key)) {
            throw new NullPointerException();
        }
        String overtime = time;
        if (timeout != null) {
            overtime = Long.toString(timeout);
        }
        final String validTime = overtime;
        Object ret = redisTemplate.execute(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) {
                Jedis jedis = (Jedis) connection.getNativeConnection();
                return jedis.eval(intoScript, 2, key, validTime);
            }
        }, true);
        long result = zero;
        if (Long.toString(minusOne).equals(ret.toString())) {
            result = minusOne;
        } else if (nil.equals(ret)) {
            result = minusTwo;
        }
        return result;
    }

    /**
     * 释放锁
     *
     * @param key
     * @return -1释放key失败  0 释放key成功  -2 执行命令失败
     */
    public long unLock(final String key) {
        if (StringUtils.isEmpty(key)) {
            throw new NullPointerException();
        }
        Object ret = redisTemplate.execute(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) {
                Jedis jedis = (Jedis) connection.getNativeConnection();
                return jedis.eval(outScript, 1, key);
            }
        }, true);
        long result = zero;
        if (Long.toString(minusOne).equals(ret.toString())) {
            result = minusOne;
        } else if (nil.equals(ret)) {
            result = minusTwo;
        }
        return result;
    }
}
